package server;

import gameframe.Chess;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Hsu琛君珩
 * @ClassName:RoomServer
 * @date 2023-12-19
 * @apiNote
 * @Version: v1.0
 */

/**
 * 请求控制命令类，用于定义房间服务中处理的各种请求类型。
 * 这些常量表示服务器可以处理的不同类型的请求。
 */
class CommandOption {
    // 请求更新棋盘状态的命令
    public static final String CHECK_UPDATE = "CHECK_UPDATE";
    // 下棋的命令，表示玩家在棋盘上放置一个棋子
    public static final String PUT_CHESS = "PUT_CHESS";
    // 悔棋的命令，表示玩家请求撤回上一步棋
    public static final String REGRET_CHESS = "REGRET_CHESS";
    // 同意悔棋的命令，表示对方玩家同意了悔棋请求
    public static final String AGREE_REGRET = "AGREE_REGRET";
    // 发送聊天消息的命令，用于在房间内的玩家间发送消息
    public static final String SEND_CHET_MESSAGE = "SEND_CHET_MESSAGE";
    // 获胜的命令，表示某方玩家赢得了游戏
    public static final String WIN = "WIN";
    // 认输的命令，表示某方玩家认输
    public static final String SURRENDER = "SURRENDER";
}

/**
 * 房间服务类，作为子服务端，负责处理房间内用户发来的各种请求。
 */
public class RoomServer implements Serializable {

    private ServerSocket roomServerSocket = null; // 房间服务端的ServerSocket，用于监听来自用户的请求。
    private int roomID; // 房间号，用于标识不同的房间。
    private int roomPort; // 房间的端口号，每个房间服务将在不同的端口上监听。
    private Model model; // 房间内的棋局模型，管理房间内的棋局状态和逻辑

    /**
     * 构造方法。
     * 初始化房间服务类的实例，为每个房间分配一个唯一的房间号。
     *
     * @param roomId 房间号，用于区分不同的房间。
     */
    public RoomServer(int roomId) {
        this.roomID = roomId;
    }

    /**
     * 启动房间服务端。
     * 在指定端口上初始化服务器套接字并开始监听客户端请求。
     * 如果房间服务已经启动，则此方法不会执行任何操作。
     *
     * @param port 房间服务端端口号，不同房间应有不同的端口。
     */
    public void startServer(int port) {
        if (roomServerSocket == null) {
            try {
                this.roomPort = port;
                this.roomServerSocket = new ServerSocket(roomPort); // 创建新的ServerSocket用于监听指定端口
                model = new Model(); // 初始化房间内的棋局模型
                beginServer(); // 启动房间内的服务线程
            } catch (IOException e) {
                e.printStackTrace(); // 打印异常信息，便于调试
            }
        }
    }

    /**
     * 启动房间服务器。
     * 此方法创建并启动一个新线程，用于持续监听房间端口上的客户端连接请求。一旦接收到请求，
     * 它将调用controlProcess方法来处理这些请求。线程内使用同步锁确保对模型的操作是线程安全的。
     */
    public void beginServer() {
        System.out.println("房间号: " + roomID + ", 端口号: " + roomPort + " 启动监听");
        new Thread(() -> {
            synchronized (model) {
                while (true) {
                    try {
                        Socket socket = roomServerSocket.accept(); // 等待并接受客户端的连接请求
                        System.out.println("收到客户端请求");
                        controlProcess(socket); // 调用方法处理接收到的请求
                    } catch (IOException e) {
                        e.printStackTrace(); // 出现IO异常时打印异常信息
                    }
                }
            }
        }).start();
    }

    /**
     * 初始化房间信息。
     * 此方法用于设置房间内棋局模型的初始状态，包括获胜方、认输方和悔棋方的初始化。
     * 它确保在每个新游戏开始时，房间的状态都是干净的，没有遗留的游戏信息。
     */
    public void init() {
        model.setWinner(Chess.SPACE); // 初始化获胜方为无（即还没有玩家获胜）
        model.setSurrenderChessColor(Chess.SPACE); // 初始化认输方为无（即还没有玩家认输）
        model.setRegretChessColor(Chess.SPACE); // 初始化悔棋方为无（即还没有玩家请求悔棋）
    }

    /**
     * 客户端请求处理过程。
     * 根据从客户端接收到的请求执行相应的操作。
     *
     * @param socket 客户端套接字
     * @throws IOException 输入输出异常
     */
    private void controlProcess(Socket socket) throws IOException {
        System.out.println("房间服务端处理请求");
        try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream())) {

            String request = in.readLine();
            System.out.println("收到客户端请求: " + request);

            // 解析请求并根据请求类型执行相应操作
            switch (request.split(":")[0]) {
                case CommandOption.PUT_CHESS:
                    // 处理下棋请求
                    putChess(out, request);
                    break;
                case CommandOption.CHECK_UPDATE:
                    // 处理检查更新请求
                    checkUpdate(out);
                    break;
                case CommandOption.WIN:
                    // 处理获胜请求
                    setWin(request);
                    break;
                case CommandOption.SEND_CHET_MESSAGE:
                    // 处理发送聊天信息请求
                    updateChetInfo(request);
                    break;
                case CommandOption.REGRET_CHESS:
                    // 处理悔棋请求
                    regretChess(request);
                    break;
                case CommandOption.AGREE_REGRET:
                    // 处理同意悔棋请求
                    agreeRegret(request);
                    break;
                case CommandOption.SURRENDER:
                    // 处理认输请求
                    surrender(request);
                    break;
                // 默认情况，处理未知请求
                default:
                    // 可以添加一些默认的处理逻辑，例如记录未知请求或发送错误响应
                    break;
            }
        }
    }

    /**
     * 检查游戏状态更新。
     * 将当前的棋局模型发送给客户端，以便客户端更新其游戏状态。
     *
     * @param out 客户端的输出流
     * @throws IOException 如果发送过程中出现IO异常
     */
    private void checkUpdate(ObjectOutputStream out) throws IOException {
        System.out.println("向客户端返回棋局信息: " + model.getChessStack());
        out.writeObject(model); // 发送当前棋局模型给客户端
    }

    /**
     * 处理下棋操作的请求。
     * 更新模型中的棋局状态，并将更新后的棋子栈发送给客户端。
     *
     * @param out 客户端的输出流
     * @param request 客户端发来的请求，包含下棋的具体信息
     * @throws IOException 如果发送过程中出现IO异常
     */
    private void putChess(ObjectOutputStream out, String request) throws IOException {
        String[] info = request.split(":");
        int chessColor = Integer.parseInt(info[1]); // 获取棋子颜色
        int x = Integer.parseInt(info[2]); // 获取棋子的x坐标
        int y = Integer.parseInt(info[3]); // 获取棋子的y坐标

        model.putChess(chessColor, x, y); // 更新棋局模型中的棋盘状态
        out.writeObject(model.getChessStack()); // 发送更新后的棋子栈给客户端
    }

    /**
     * 设置获胜方。
     * 根据客户端的请求更新房间内的棋局获胜方信息。
     *
     * @param request 客户端发送的获胜请求，包含获胜方的信息
     */
    private void setWin(String request) {
        String[] info = request.split(":");
        int winChessColor = Integer.parseInt(info[1]); // 获取获胜方的棋子颜色
        System.out.println("玩家 " + winChessColor + " 赢了");
        model.setWinner(winChessColor); // 更新模型中的获胜方信息
    }

    /**
     * 更新聊天信息。
     * 根据客户端的请求更新房间内的聊天信息。
     *
     * @param request 客户端发送的聊天信息请求，包含发送者和消息内容
     */
    private void updateChetInfo(String request) {
        String[] info = request.split(":");
        String userName = info[1]; // 获取发送聊天信息的用户名
        String chetStr = info[2]; // 获取聊天信息内容
        model.updateChatInfo(userName, chetStr); // 更新模型中的聊天信息
        System.out.println("用户 " + userName + " 发送了聊天信息: " + chetStr);
    }

    /**
     * 处理悔棋请求。
     * 更新模型中的悔棋方信息。
     *
     * @param request 客户端发送的悔棋请求，包含请求悔棋方的信息
     */
    private void regretChess(String request) {
        String[] info = request.split(":");
        int chessColor = Integer.parseInt(info[1]); // 获取请求悔棋的玩家颜色
        System.out.println("玩家 " + chessColor + " 请求悔棋");
        model.setRegretChessColor(chessColor); // 更新模型中的悔棋方信息
    }

    /**
     * 处理同意悔棋的请求。
     * 如果同意悔棋，则执行悔棋操作。
     *
     * @param request 客户端发送的同意悔棋请求
     */
    private void agreeRegret(String request) {
        String[] info = request.split(":");
        boolean agree = Integer.parseInt(info[1]) == 1;
        if (agree) {
            model.regretChess(); // 执行悔棋操作
            System.out.println("悔棋请求被同意");
        }
        model.setRegretChessColor(Chess.SPACE); // 重置悔棋方信息
    }

    /**
     * 处理认输请求。
     * 更新模型中的认输方信息。
     *
     * @param request 客户端发送的认输请求，包含认输方的信息
     */
    private void surrender(String request) {
        String[] info = request.split(":");
        int chessColor = Integer.parseInt(info[1]); // 获取认输的玩家颜色
        System.out.println("玩家 " + chessColor + " 请求认输");
        model.setSurrenderChessColor(chessColor); // 更新模型中的认输方信息
    }

}

